package com.abel.bigwater.dataserver.model.hgd

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.dataserver.model.*
import io.netty.buffer.ByteBuf
import org.joda.time.DateTime
import org.slf4j.LoggerFactory
import java.nio.charset.Charset
import java.util.*
import kotlin.collections.ArrayList

/**
 * <pre>
 * 智慧物联 智慧水务
 * 鸿道上发报文协议 V1.3.4
 * AB CD 00 7D 00 66 11 05 24 16 15 03 0F 02 CE 00 05
 * 起 始 符 长 度 站 号 年 月 日 时 分 个 数 间 隔 电池电压 上发次数
 * 125 00102 起始数据日期 3 15 分钟 7.18V 5 次
 * 1E D115;1.4;1.0.11 00 00 00 00 00 00 00 00
 * 信号强度 设备名称+硬件版本号+软件版本号=20 字节 保留 8 字节
 * 30 D115 1.4 1.0.11
 * 01 01 0B 01 00 00 37 07 00 00 38 07 00 00 48 07 00 00
 * 类型 通道号 昨 日 流 量 数据 1：累计流量 数据 2：累计流量 数据 3：累计流量
 * P1 L1:00000267 P1:00001847 P1:00001848 P1:00001864
 * 01 02 0B 00 00 00 01 02 00 00 03 02 00 00 05 02 00 00
 * 类型 通道号 昨 日 流 量 数据 1：累计流量 数据 2：累计流量 数据 3： 累计流量
 * P2 L2:00000011 P2:00000513 P2:00000515 P2:00000517
 * 02 03 01 01 00
 * 类型 通道号 状态 1 状态 2 状态 3
 * K3 1 1 0
 * 03 04 01 01 00
 * 类型 通道号 状态 1 状态 2 状态 3
 * B4 1 1 0
 * 04 05 3E 4C CC CD 3E 80 00 00 3E 8F 5C 29
 * 类型 通道号 模拟量数据 1 模拟量数据 2 模拟量数据 3
 * A5 0.2 0.25 0.28智慧物联 智慧水务
 * 05 0D 0012 0013 4011
 * 类型 通道号 Q 数据 1 Q 数据 2 Q 数据 3
 * Q13
 * 06 0E 00126789 00126489 00326780
 * 类型 通道号 M 数据 1 M 数据 2 M 数据 3
 * M14
 * 6A 0D 0A
 * 累加和校验 结 束 符
 * “\r\n”
 * 注释：
 * 起始符 → 2 字节 HEX，固定为 0xAB， 0xCD；
 * 长度 → 2 字节 HEX，高位在前，从报文中的站号字段开始，不算结尾的 OD OA 的数据个数；
 * 站号 → 2 字节 HEX，高位在前；
 * 起始时间 → 5 个字节 BCD，起始时间格式：年月日时分；
 * 数据个数 → 1 字节 HEX，该报文所包含每个传感器的数据个数；
 * 数据间隔 → 1 字节 HEX，数据打包间隔 HEX 转换后的十进制数；
 * 电池电压 → 2 字节 HEX，高位在前，除以 100 后为电池电压实际数值；
 * 设备名称 + 硬件版本 + 软件版本 → 20 字节 ASCII 码，不到 20 字节后面补空格；
 * 类型 → 1 字节 HEX， 表示通道类型；
 * 类型： 1--脉冲量 2--开关量 3--报警量 4--模拟量
 * 5--虚拟 Q 通道 6--虚拟 M 通道
 * 通道号 → 1 字节 HEX，表示通道号；
 * 昨日流量 → 4 字节 HEX，低位在前，以最后一时刻为基准的前一天的日流量，例如：从 23 日 00 点
 * 到 24 日 00 点的流量；
 * 累计流量 1 → 4 字节 HEX，低位在前，起始时间的累计流量；
 * 累计流量 2 → 4 字节 HEX，低位在前，起始时间 +（间隔×1）的累计流量；
 * ……
 * 累计流量 n → 4 字节 HEX，低位在前，起始时间 +（间隔×（ n-1））的累计流量；智慧物联 智慧水务
 * 模拟量数据 1 → 4 字节 HEX，为浮点数，符合 IEEE754，起始时间的模拟量；
 * 模拟量数据 2 → 4 字节 HEX，为浮点数，符合 IEEE754，起始时间 +（间隔×1）的模拟量；
 * ……
 * 模拟量数据 1 → 4 字节 HEX，为浮点数，符合 IEEE754，起始时间 +（间隔×（ n-1））的模拟量；
 * Q 通道数据 → 2 字节 HEX，按二进制解码，如 0012 表示 Qx_15-Qx_0 分别为 0000000000010010;
 * M 通道数据 → 4 字节 HEX，为 BCD 码，如 0012678 表示 Mx_1-Mx_8 分别为 0012678;
 * 累加和校验位 → 1 个字节 HEX， AB – 80 数据累加，不计超过 0xFF 部分；
 * 结束符 → 2 字节 HEX，固定为 0x0D， 0x0A。
 * 制 表： 审 核： 批 准：
 * &#64;author Abel
</pre> *
 */
open class HgdUdpData : HgdaoBase {

    /** with appended blank ' '.  */
    var devInfoSoft: String? = null

    var channelList: ArrayList<HgdChannel<*>> = ArrayList()

    /**
     * the checksum to set
     */
    var checksum: Short = 0

    // END: \r\n

    /**
     *
     */
    constructor() : super(HgdaoBase.TYPE_UDP)

    /**
     * @param type
     */
    constructor(type: String) : super(type)

    @Throws(PacketException::class)
    override fun parseBuffer(buf: ByteBuf, ignoreChecksum: Boolean): Boolean {
        // AB CD
        buf.duplicate().apply {
            val h1 = readByte()
            val h2 = readByte()
            if (h1 != HEAD_1.toByte() || h2 != HEAD_2.toByte()) {
                return false
            }
        }

        // verify length
        if (buf.readableBytes() < 2 + 2 + MIN_DATA_LENGTH + TAIL_LENGTH) {
            throw PacketNotEnoughException("not enough for ${javaClass.simpleName}: ${buf.readableBytes()}")
        }

        buf.duplicate().apply {
            // header.
            skipBytes(2)

            // length.
            length = readUnsignedShort()
            if (length < MIN_DATA_LENGTH) {
                throw PacketDataLenException("Expect length at least $MIN_DATA_LENGTH but got: $length.")
            }

            // not enough bytes
            if (readableBytes() < length + 2) {
                throw PacketNotEnoughException("${buf.readableBytes()}")
            }
        }

        // verify checksum
        if (!ignoreChecksum) {
            buf.duplicate().readBytes(2 + 2 + length + 2).apply {
                val cs = ByteArray(4 + length - 1).also {
                    readBytes(it)
                }.sum().and(0xFF)

                checksum = readUnsignedByte()

                if (checksum != cs.toShort()) {
                    throw PacketChecksumException("$checksum != $cs")
                }

                val t1 = readByte()
                val t2 = readByte()
                if (t1 != 0x0D.toByte() || t2 != 0x0A.toByte()) {
                    throw PacketChecksumException("invalid tail: $t1 $t2")
                }
            }
        }

        // parse data.
        buf.readBytes(2 + 2 + length + 2).apply {
            // store buffer
            temp = ByteArray(readableBytes()).also { duplicate().readBytes(it) }
            fmt = BcdString.hexString(temp!!, true)

            // parsing
            skipBytes(4)

            stationInt = readUnsignedShort()
            station = String.format("%1$05d", stationInt)

            startTimeStr = BcdString(ByteArray(5).also {
                readBytes(it)
            }, false)

            // count.
            count = readUnsignedByte()

            // period
            period = readUnsignedByte()

            // voltage
            voltage = readUnsignedShort()

            // upload times.
            uploadTimes = readUnsignedShort()

            // signal
            signal = readUnsignedByte()

            // dev info & soft info.
            devInfoSoft = readBytes(20).toString(Charset.defaultCharset())

            // keep future.
            skipBytes(8)

            // channels.
            while (readableBytes() > 1 + TAIL_LENGTH) {
                val dtype = HgdChannel.parseType(this)
                lgr.info("channel type: $dtype")
                val hc: HgdChannel<*>
                when (dtype) {
                    HgdChannelType.PULSE_TYPE -> hc = PulseChannel(count)

                    HgdChannelType.K_TYPE -> hc = KChannel(count)

                    HgdChannelType.ANALOG_TYPE -> hc = AnalogChannel(count)

                    HgdChannelType.Q_TYPE -> hc = QChannel(count)

                    HgdChannelType.M_TYPE -> hc = MChannel(count)

                    HgdChannelType.B_TYPE -> hc = BChannel(count)

                    else -> throw PacketInternalException("unknown channel abbr: $dtype")
                }

                if (!hc.parseBuffer(this)) break

                channelList.add(hc)
            }
        }

        return true
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    override fun toString(): String {
        return String.format("%s[%s->len=%s, st=%s, buf:%s]",
                javaClass.simpleName, type, length, station, fmt)
    }

    companion object {
        private val lgr = LoggerFactory.getLogger(HgdUdpData::class.java)

        private val MIN_DATA_LENGTH = 2 + 5 + 1 + 1 + 2 + 2 + 1 + 20 + 8 + 1
        private val TAIL_LENGTH = 2

        // start[H]: AB CD.
        private val HEAD_1 = 0xAB
        private val HEAD_2 = 0xCD

        /**
         *  11 05 24 16 15
         */
        fun BcdString.toHgdDate(): Date {
            return DateTime(2000 + bcdArray!![0], bcdArray!![1].toInt(), bcdArray!![2].toInt(),
                    bcdArray!![3].toInt(), bcdArray!![4].toInt(), 0).toDate()
        }

        /**
         *  11 05 24 16 15
         */
        fun fromHgdDate(date: DateTime?): BcdString? {
            return if (date == null) null
            else BcdString(arrayOf(
                    date.year.rem(100).toByte(),
                    date.monthOfYear.toByte(), date.dayOfMonth.toByte(),
                    date.hourOfDay.toByte(), date.minuteOfHour.toByte()
            ).toByteArray(), true)
        }
    }
}
